from unittest import TestCase

from vcs.models import Repository
from shared import repo as baserepo


def repo(name, **kwargs):
    return baserepo('svn', name, **kwargs)


class SVNTestCase(TestCase):
    def setUp(self):
        # Effectively wipe database before each test.
        Repository.objects.all().delete()


class SVNBranchCreation(SVNTestCase):
    def testPlain(self):
        """
        A simple repository without any folders will get a single master branch.
        """
        r = repo('plain')
        r.refresh()
        self.assertEqual(r.branch_names, ['master'])

    def testStandard(self):
        """
        A "standard" repository with the exact trunk/branches/tags setup will
        turn into trunk + any other branches in /branches/.
        """
        r = repo('standard')
        r.refresh()
        self.assertEqual(set(r.branch_names), set(['trunk', 'branch1']))

    def testCustomRootBranchPath(self):
        """
        A branch with a custom branch path of '/' will have all root-level
        folders as branches, and no tags.
        """
        r = repo('root-branch-path', branch_path='/')
        r.refresh()
        self.assertEqual(r.branch_names, ['branch1', 'branch2'])

    def testCustomNonrootBranchPath(self):
        """
        A branch with custom branch path of '/branches' and with a '/trunk'
        will have trunk + the contents of /branches added to its branch list.
        All other folders are currently ignored (!)
        """
        r = repo('custom-branch-path', branch_path='/branches')
        r.refresh()
        self.assertEqual(r.branch_names, ['branch1', 'branch2', 'trunk'])


class SVNCommitCreation(SVNTestCase):
    def testEmptyRepo(self):
        """
        Make sure loading an empty/new repository works correctly -- no commits
        and the default 'master' branch.
        """
        r = repo('empty')
        r.refresh()
        self.assertEqual(r.commits.count(), 0)
        self.assertEqual(r.branch_names, ['master'])

    def testSingleCommit(self):
        """
        A lonely single commit will have no parents or children and will be
        HEAD of the master branch.
        """
        r = repo('single')
        r.refresh()
        self.assertEqual(r.commits.count(), 1)
        c = r.commits.all()[0]
        self.assertEqual(c.parents.count(), 0)
        self.assertEqual(c.children.count(), 0)
        self.assertEqual(r.branches.get(name='master').head, c)

    def testTwoCommits(self):
        """
        Two commits (both in master) will have a parent/child relationship,
        and commit #2 will be HEAD.
        """
        r = repo('couple')
        r.refresh()
        self.assertEqual(r.commits.count(), 2)
        c2, c1 = r.commits.all() # ordered by newest first
        self.assertEqual(c2.parent, c1)
        self.assertEqual(c1.children.all()[0], c2)
        self.assertEqual(c2.parents.count(), 1)
        self.assertEqual(c1.children.count(), 1)

    def testMultipleRefresh(self):
        """
        Multiple back-to-back calls to refresh() should not ever cause errors.
        Test a number of repos to catch edge cases.
        """
        r = repo('couple')
        r.refresh()
        r.refresh()
        r.delete()
        r2 = repo('single')
        r2.refresh()
        r2.refresh()
        r3 = repo('inactive-branches')
        r3.refresh()
        r3.refresh()
        r4 = repo('standard')
        r4.refresh()
        r4.refresh()

    def testCommitsAndBranches(self):
        """
        Ensure full expected set of branches, commits for a middling complex
        repository.
        """
        r = repo('standard')
        r.refresh()
        self.assertEqual(r.branches.count(), 2)
        self.assertEqual(r.active_branches.count(), 2)
        self.assertEqual(r.inactive_branches.count(), 0)
        self.assertEqual(r.commits.count(), 3)
        self.assertEqual(r.branches.get(name='trunk').commits.count(), 3)
        self.assertEqual(r.branches.get(name='branch1').commits.count(), 2)

    def testMultipleBranchCommits(self):
        """
        Commits involving >1 branch will correctly "belong to" all involved
        branches (via m2m).
        """
        r = repo('merge')
        r.refresh()
        # Sanity tests
        self.assertEqual(r.branches.count(), 2)
        self.assertEqual(r.active_branches.count(), 2)
        r1, r2, r3, r4 = r.commits.all().order_by('identifier')
        # Second and third commits are 1 branch each
        self.assertEqual(r2.branches.count(), 1)
        self.assertEqual(r2.branches.all()[0].name, 'trunk')
        self.assertEqual(r3.branches.count(), 1)
        self.assertEqual(r3.branches.all()[0].name, 'branch1')
        # Fourth commit is the one involving >1 branch
        self.assertEqual(r4.branches.count(), 2)


class SVNBranchDeletion(SVNTestCase):
    def testActiveInactiveBranches(self):
        """
        Make sure a repository with some removed branches correctly deals with
        the concept of inactive vs inactive vs total branches.
        """
        r = repo('inactive-branches')
        r.refresh()
        self.assertEqual(r.active_branches.count(), 1)
        self.assertEqual(r.inactive_branches.count(), 1)
        self.assertEqual(r.branches.count(), 2)
